Introduction
Recently, a company's website was hijacked and used to serve malware, with a legitimate-seeming TLS certificate. The hijack didn't touch the webserver itself. Instead, it attacked the routing that directs traffic towards the server. I'm going to describe how it happened and how you might protect against it.
Let's say you go to visit my website at https://example.com/. First, your
computer looks up the IP address associated with the site, and tries to connect
to that address - first via your house's router, then via your ISP's router,
then via some other routers...
Each router is basically a multi-armed sign-post which says "If you're heading towards this network, go down this piece of fibre optic. If you're going to that network, use that fibre", and so on. Each router decides where its signs point based on messages which are shared between routers.
It's occasionally possible for a powerful adversary to send out fake router messages, which means that routers start sending traffic the wrong way. Let's say that Mallory has done this, and your traffic intended for my domain can end up at her server instead!
TLS Certificates
When your traffic arrives at Mallory's server, because it's using HTTPS, it'll be expecting a TLS certificate that it can trust. So Mallory will need to generate a trusted certificate. The standard way to do this is via LetsEncrypt, a trusted Certificate Authority which will hand out TLS certificates to people who can prove they have some level of ownership of a matching domain name.
We know that Mallory can route traffic intended for my domain name and its IP
address to her own server. Provided her router hack is comprehensive enough to
affect LetsEncrypt, she can use the http-01 method to assert that she can
control content on her impersonating server, and LetsEncrypt will give her a
TLS certificate.
Her server returns that certificate to your browser and you go on looking at, or putting data into, a malicious website, none the wiser. You even get a nice padlock in your browser to tell you that you're secure!
Stopping Mallory with CAA
How do I stop Mallory from doing this? I can assert who is allowed to issue
TLS certificates for example.com using a CAA record.
I could choose to pay for TLS certificates from Digicert, and tell the Internet
that only Digicert is alowed to issue TLS certificates for our domain. This
just requires me to set a CAA issue record of digicert.com. Then we can
rely on Digicert's authentication systems that will let me sign certificates
but not Mallory.
example.com. 3600 IN CAA 0 issue "digicert.com"
But that costs money, and I'm a cheapskate, so I want to use LetsEncrypt. But if I allow LetsEncrypt to issue certificates, then Mallory can generate them for my server if she hijacks the routing tables!
ACME Client Accounts
When you use an ACME client such as lego to get a certificate signed by LetsEncrypt, an account is used. This happens in the background, and I'd never noticed it before! Hiding somewhere in your ACME client will be an account URI, and a private key used for authentication to the server.
For lego, the account URI is stored in a file called account.json; for
Traefik it's in acme.json. You may have multiple URIs for multiple accounts
depending on how you've configured your client, so pick the one which matches
your domain!
You can specify two things in your CAA record: that only LetsEncrypt is allowed
to generate TLS certificates for your domain, and that only a specific
LetsEncrypt account can do it, by using the accounturi parameter.
example.com. 3600 IN CAA 128 issue "letsencrypt.org;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/99999999"
Note that I've changed the 0 to 128 in the CAA record. This means "if you don't understand any directive, don't issue the certificate" and protects me against typos in the record.
Now only LetsEncrypt account 99999999, authenticated by its key, is permitted
to generate TLS certificates for example.com. Any other issuer, or any other
LetsEncrypt account, should generate an error.
In my case I have two ACME clients - lego generates the TLS certs used by my mailserver, and Traefik generates the certs used by my webserver. This isn't a problem, I just add two CAA records for the same domain, with different URIs.
Finally, you can add a CAA iodef record, which means that if somebody
unauthorised tries to generate a certificate, you should be notified:
example.com. 3600 IN CAA 0 iodef "mailto:abuse@example.com"
Sidenotes
There's also a CAA issuewild record which allows you to set separate policies
for wildcard TLS certificates. And you can also specify a validationmethod
parameter which limits which ACME method, such as HTTP or DNS, can be used to
validate ownership of a domain.
Another possible attack for Mallory would be to divert the route to
example.com's nameservers. That way she could not only redirect example.com
to any IP she wanted, she could also provide whatever CAA records she wants to
make sure the TLS cert was issued. However, this can be defeated using
DNSSEC, which uses a chain of digital signing from the root servers through
to my registrar. LetsEncrypt should be able to spot the unsigned or mis-signed
DNS records and refuse to issue.
Summary
How likely is this attack? Not very, for me - I'm not likely to have someone try and hijack traffic to my random self-hosted server, because I'm not that interesting to the kind of people who have the resources to do this.
It's also not something you have to worry about too much if you're, say, connecting to a random wifi network. It's trivial for the provider of that network to carry out routing attacks on your traffic to PayPal or Facebook or whatever. But they won't be able to get a valid TLS certificate issued for those domains, so you'll get a browser error. And thanks to another safety technology called HSTS, you won't be able to bypass that error!
After I set up CAA records, I tried generating a certificate using a different LetsEncrypt account:
2026-09-03T22:03:30.812835274Z ERROR Error error="obtain certificate: resolver: one or more domains had a problem: [example.com: invalid authorization: acme: error: 403 :: urn:ietf:params:acme:error:caa :: While processing CAA for example.com: CAA record for example.com prevents issuance]"
I have however not yet received an email from LetsEncrypt despite my iodef record.
At my job, we have websites which are used to transfer personal data, and I'll be considering whether CAA records are an appropriate control to prevent this kind of attack.